package com.xuecheng.media.jobhandler;

import com.adan.base.utils.Mp4VideoUtil;
import com.xuecheng.media.model.po.MediaProcess;
import com.xuecheng.media.service.MediaFileService;
import com.xuecheng.media.service.MediaProcessService;
import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * @version: 1.0
 * @Author: adan
 * @description: 视频处理任务类
 * @date: 2023-06-20 16:03
 */
@Slf4j
@Component
public class VideoTask {

    @Resource
    MediaFileService mediaFileService;
    @Resource
    MediaProcessService mediaProcessService;

    @Value("${videoprocess.ffmpegpath}")
    String ffmpegpath;

    @XxlJob("videoJobHandler")
    public void videoJobHandler() {
        // 分片参数
        // 分片序号，从0开始
        int shardIndex = XxlJobHelper.getShardIndex();
        // 分片总数
        int shardTotal = XxlJobHelper.getShardTotal();
        // 取出可用的cpu核心数。这个方法返回可用处理器的Java虚拟机的数量
        int processors = Runtime.getRuntime().availableProcessors();
        // 每次任务处理视频的数量不要超过cpu核心数
        List<MediaProcess> mediaProcessList = mediaProcessService.getMediaProcessList(shardIndex, shardTotal, processors);
        // 视频处理的数量
        int size = mediaProcessList.size();
        log.debug("取到任务数：{}", size);
        if (size<=0){
            return;
        }
        // 创建size个线程的线程池。这是执行器工具类方法创建线程池，也可以自定义线程池ThreadPoolExecutor配置类注入方式
        ExecutorService threadPool = Executors.newFixedThreadPool(size);
        // 计数器。初始值是线程数，每执行完一个线程，计数器减一
        // 使用计数器是为了后面给线程阻塞，保证所有线程执行结束后或者超时时间后再结束整个任务方法。
        // 不使用计数器的话，下面代码会直接循环开启线程，不等线程结束运行完整个任务方法就结束了，线程就都死了（自定义线程池就不用考虑这种情况），不能控制超时时间。
        // 也可以不使用计数器，使用自定义线程池ThreadPoolExecutor配置类注入方式，能指定过期时间、核心线程数、最大线程数等
        CountDownLatch countDownLatch = new CountDownLatch(size);
        // 将处理任务加入线程池
        mediaProcessList.forEach(mediaProcess -> {
            // 列表里每个任务启动一个线程
            threadPool.execute(() -> {
                try {
                    // 任务id
                    Long taskId = mediaProcess.getId();
                    // 开启任务，乐观锁防止重复消费，修改任务状态为“处理中”
                    boolean b = mediaProcessService.startTask(taskId);
                    if (!b) {
                        return;
                    }
                    log.debug("开始执行任务:{}", mediaProcess);
                    // 下边是处理逻辑
                    // 桶
                    String bucket = mediaProcess.getBucket();
                    // 存储路径
                    String filePath = mediaProcess.getFilePath();
                    // 原始视频的md5值
                    String fileId = mediaProcess.getFileId();
                    // 原始文件名称
                    String filename = mediaProcess.getFilename();
                    // 将要处理的文件下载
                    File file = mediaFileService.downloadFileFromMinIO(bucket, filePath);
                    if (file == null) {
                        // 下载待处理文件失败，保存任务结果
                        log.debug("下载待处理文件失败,originalFile:{}", mediaProcess.getBucket().concat(mediaProcess.getFilePath()));
                        mediaProcessService.saveProcessFinishStatus(taskId, "3", fileId, null, "下载待处理文件失败");
                        return;
                    }
                    // 创建mp4临时文件
                    File fileMp4 = null;
                    try {
                        fileMp4 = File.createTempFile("mino", ".mp4");
                    } catch (IOException e) {
                        log.error("创建mp4临时文件失败");
                        mediaProcessService.saveProcessFinishStatus(mediaProcess.getId(),
                                "3", fileId, null, "创建mp4临时文件失败");
                        return;
                    }
                    // 处理视频
                    Mp4VideoUtil mp4VideoUtil = new Mp4VideoUtil(ffmpegpath, file.getAbsolutePath(), fileMp4.getName(), fileMp4.getAbsolutePath());
                    String result = mp4VideoUtil.generateMp4();
                    if (!"success".equals(result)) {
                        // 记录错误信息
                        log.error("处理视频失败,视频地址:{},错误信息:{}", bucket + filePath, result);
                        mediaProcessService.saveProcessFinishStatus(mediaProcess.getId(), "3", fileId, null, result);
                        return;
                    }
                    // 将mp4上传至minio
                    String mimeType = "video/mp4";
                    String filePathMinio = getFilePathByMd5(fileId, ".mp4");
                    //访问url
                    String url = "/" + bucket + "/" + filePathMinio;
                    boolean b1 = mediaFileService.uploadFileToMinio(bucket, mimeType, filePathMinio, fileMp4.getAbsolutePath());
                    if (!b1){
                        // 记录错误信息
                        log.error("上传minio失败，taskId:{}", taskId);
                        mediaProcessService.saveProcessFinishStatus(mediaProcess.getId(), "3", fileId, null, "上传minio失败");
                        return;
                    }
                    //上传成功，删除临时文件
                    fileMp4.delete();
                    //将url存储至数据，并更新状态为成功，并将待处理视频记录删除存入历史
                    mediaProcessService.saveProcessFinishStatus(taskId, "2", fileId, url, null);
                } finally {
                    //finally里，不管成功还是失败计数器都减一
                    countDownLatch.countDown();
                }
            });
        });
        //计时器减成0之前或者指定时间之前一直阻塞。给一个充裕的超时时间,防止无限等待，到达超时时间还没有处理完成则结束任务
        try {
            countDownLatch.await(10, TimeUnit.MINUTES);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
    /**
     * 得到合并后的文件的地址
     * @param fileMd5 文件id即md5值
     * @param fileExt 文件扩展名
     * @return
     */
    private String getFilePathByMd5(String fileMd5,String fileExt){
        return   fileMd5.charAt(0) + "/" + fileMd5.charAt(1) + "/" + fileMd5 + "/" +fileMd5 +fileExt;
    }

}
